package sia.tp1.genotype;

import java.util.List;

import sia.tp1.Problem;
import sia.tp1.operator.Not;
import sia.tp1.operator.Operator;


public class OperatorTree {

	static double explodeP;
	static int maxHeight;
	Operator operator;
	int height;
	int level;
	OperatorTree leftTree;
	OperatorTree rightTree;
	
	public OperatorTree(int maxHeight, int currentLevel) {
		
		level = currentLevel;
		if(maxHeight <= 0 || Math.random() > explodeP) {
			operator =  Problem.generateConstantOperator();
			height = 0;
			return;
		}
		
		operator = Problem.generateOperator();
		if ( !(operator instanceof Not) )
			leftTree = new OperatorTree(maxHeight - 1, currentLevel + 1);
		else
			leftTree = new OperatorTree(0, currentLevel + 1);
		
		rightTree = new OperatorTree(maxHeight - 1,  currentLevel + 1);
		height = Math.max(leftTree.height + 1, rightTree.height + 1);
	}
	
	public boolean eval(List<Boolean> input) throws Exception {
		if(leftTree == null || rightTree == null) {
			//Si no tiene hijos es si o si constante, retorno el constante
			return operator.eval(input);
		}
		return operator.eval(input, leftTree.eval(input), rightTree.eval(input));
	}

	public void mutateChilds(double mutationP) {
		if( leftTree == null || rightTree == null )
			return;
			
		//Muto la rama izquierda, si no soy un nodo NOT
		if( !(operator instanceof Not) ) {
			if(Math.random() < mutationP) {
				leftTree = new OperatorTree(maxHeight - (level + 1), level + 1);
			} else if(leftTree != null){
				leftTree.mutateChilds(mutationP);
			}
		}
		
		//La rama derecha muta siempre
		if(Math.random() < mutationP) {
			rightTree = new OperatorTree(maxHeight - (level + 1), level + 1);
		} else if(rightTree != null){
			rightTree.mutateChilds(mutationP);
		}
	}

	public int getNodesCount() {
		if( leftTree == null || rightTree == null )
			return 1;
		
		if( !(operator instanceof Not) )
			return 1 + this.leftTree.getNodesCount() + this.rightTree.getNodesCount();
		
		return 1 + this.rightTree.getNodesCount();
	}
	
	@Override
	public OperatorTree clone() {
		OperatorTree clone = new OperatorTree(level, level + 1);

		clone.operator = operator.clone();
		
		if( leftTree != null )
			clone.leftTree = leftTree.clone();
		else
			clone.leftTree = null;
		
		if( rightTree != null )
			clone.rightTree = rightTree.clone();
		else
			clone.rightTree = null;
		
		return clone;
	}
	
	public OperatorTree GetNodeWrapper( Integer crossPoint ) {
		int[] a = new int[1];
		a[0] = crossPoint;
		return getNode(a);
	}

	private OperatorTree getNode(int[] crossPoint) {

		if( crossPoint[0] == 0 )
			return this;
		
		if( leftTree == null || rightTree == null )
			return null;
		
		OperatorTree leftSubTree = null;
		if( !(operator instanceof Not) ) {
			crossPoint[0] -= 1;
			leftSubTree = leftTree.getNode(crossPoint);
		}
		
		if( leftSubTree == null ) {
			crossPoint[0] -= 1;
			return rightTree.getNode(crossPoint);
		}
		
		return leftSubTree;
	}
	
	@Override
	public String toString() {
		if(leftTree == null && rightTree == null)
			return "[ " + operator + " ]";
		if( operator instanceof Not )
			return "( " + operator + " " + rightTree + " )";
		return "( " + operator + " " + leftTree + " " + rightTree + " )";
	}

	public int getHeight() {
		return height;
	}
	
	public void updateTree(int fromLevel) {
		level = fromLevel;
		
		if( leftTree == null || rightTree == null ) {
			height = 0;
			return;
		}

		leftTree.updateTree(level + 1);
		rightTree.updateTree(level + 1);
		
		height = Math.max(leftTree.height, rightTree.height) + 1;
	}
}
